package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.common.auth.entity.AppInfo;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @package com.leyou.auth.service
 * @description:
 * @author: 许超越
 * @date 2019/7/7 17:08
 * @version: V1.0
 */
@Service
@Slf4j
public class AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties prop;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ApplicationInfoMapper appMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    private static final String USER_ROLE = "guest";

    /**
     * 登入
     *
     * @param username 用户名
     * @param password 用户密码
     * @param response
     */
    public void login(String username, String password, HttpServletResponse response) {
        try {
            //根据用户名和密码查询用户
            UserDTO user = userClient.queryUserByUsernameAndPassword(username, password);
            //封装userInfo
            UserInfo info = new UserInfo(user.getId(), user.getUsername(), USER_ROLE);
            //生成token
            String token = JwtUtils.generateTokenExpireInMinutes(info, prop.getPrivateKey(), prop.getUserTokenProperties().getExpire());
            //将token写入cookie
            writeCookie(response, token);
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
    }

    /**
     * 在浏览器中添加cookie
     *
     * @param response
     * @param token
     */
    private void writeCookie(HttpServletResponse response, String token) {
        CookieUtils.newCookieBuilder()
                .response(response) //响应cookie
                .domain(prop.getUserTokenProperties().getCookieDomain()) //设置cookie的域
                .name(prop.getUserTokenProperties().getCookieName()) //设置cookie的name
                .httpOnly(true) //防止跨站脚本攻击
                .value(token).build(); //添加cookie的值
    }


    /**
     * 登入成功验证登入并返回用户信息
     *
     * @param request
     * @param response
     * @return
     */
    public UserInfo verifyUser(HttpServletRequest request, HttpServletResponse response) {
        try {
            //获取cookie中记录的token
            String token = CookieUtils.getCookieValue(request, prop.getUserTokenProperties().getCookieName());
            //获取token中载荷的信息
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
            //获取token的id判断是否已经过期
            Boolean bool = redisTemplate.hasKey(payload.getId());
            if (bool != null && bool) {
                //过期的token，抛出未授权异常
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }
            //获取过期的时间
            Date expiration = payload.getExpiration();
            //判断过期时间到离现在时间是否小于最大刷新剩余时间
            if (expiration.getTime() - System.currentTimeMillis() < prop.getUserTokenProperties().getMaxRefreshInterval()) {
                //过了刷新剩余最大时间生成新的token
                token = JwtUtils.generateTokenExpireInMinutes(payload.getUserInfo(), prop.getPrivateKey(), prop.getUserTokenProperties().getExpire());
                //将token写入cookie
                writeCookie(response, token);
            }
            //返回用户信息
            return payload.getUserInfo();
        } catch (Exception e) {
            // 抛出异常，证明token无效，直接返回401
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }


    /**
     * 登出
     * @param request
     * @param response
     */
    public void logout(HttpServletRequest request, HttpServletResponse response) {
        try {
            //获取token
            String token = CookieUtils.getCookieValue(request, prop.getUserTokenProperties().getCookieName());
            //判断token是否存在
            if (token == null) {
                //用户未登入
                throw new RuntimeException("用户未登入");
            }
            //获取cookie的id和剩余存活时间
            Payload<Object> payload = null;
            try {
                payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey());
            } catch (Exception e) {
                throw new RuntimeException("用户未登入");
            }
            String id = payload.getId();
            long survivalTime = payload.getExpiration().getTime() - System.currentTimeMillis();
            //写入Redis中，添加称为黑名单
            redisTemplate.opsForValue().set(id, "1", survivalTime, TimeUnit.MILLISECONDS);
            //删除cookie
            CookieUtils.deleteCookie(prop.getUserTokenProperties().getCookieName(), prop.getUserTokenProperties().getCookieDomain(), response);
        } catch (RuntimeException e) {
            log.error("用户登出失败，原因：{}", e.getMessage());
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }


    /**
     * 微服务认证并申请令牌
     *
     * @param id 服务id
     * @param secret 密码
     * @return JWT，包含载荷数据就是AppInfo
     */
    public String authenticate(Long id, String secret) {
        //根据id查询用户信息
        ApplicationInfo app = appMapper.selectByPrimaryKey(id);
        //判断是否查询到结果
        if (app == null) {
            //id不能存在，抛出异常
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
        //校验密码是否正确
        if (!passwordEncoder.matches(secret, app.getSecret())) {
            //密码不正确
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
        //查询app的权限
        List<Long> targetIdList = appMapper.queryTargetIdList(id);
        //将信息封装进appInfo
        AppInfo appInfo = new AppInfo();
        appInfo.setId(id);
        appInfo.setServiceName(app.getServiceName());
        appInfo.setTargetList(targetIdList);
        //生成token并返回
        return JwtUtils.generateTokenExpireInMinutes(appInfo, prop.getPrivateKey(), prop.getAppTokenProperties().getExpire());
    }
}
